package gpinterpreter.stack;

import gpinterpreter.Instruction;
import gpinterpreter.Interpreter;
import gpinterpreter.vector.VecInstruction;
import gpinterpreter.vector.VecInterpreter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import primitives.cluster.ClusterHead;
import primitives.cluster.ClusterNode;
import primitives.cluster.ClusterUtils;
import primitives.cluster.IClusterLevel;
import primitives.util.UndefinedIndexException;

/**
 *
 * @author mat
 */
public class StackInterpreter extends Interpreter<StackInstruction> {

    private Stack<IClusterLevel> stack;
    private HashSet<Integer> seen;

    public StackInterpreter(List<StackInstruction> p, ClusterHead tree) {
		super(p, tree);
		stack = new Stack<IClusterLevel>();
		seen = new HashSet<Integer>();
    }
	
	private int wastage=0;
	
	public int getWastage(){
		return wastage;
	}

    public void execute(StackInstruction nextInstruction) {

	IClusterLevel c1, c2;
	int op = nextInstruction.getOperand();
	switch (nextInstruction.getType()) {

	    // PUSH n
	    // where n is a base cluster ID
	    // stack = [clusternode $n]:stack
	    case PUSH:


		//don't do anything if the operand has already been put on the stack at some point.
		if(seen.contains(op)){
			wastage++;
			break;
		}


		if (op == 0) {
		    Logger.getLogger(StackInterpreter.class.getName()).log(Level.INFO, "Attempt to merge with head of cluster tree aborted");
		    break;
		}
		try {
		    c1 = ClusterUtils.lookupIndexInTree(op, tree);
		} catch (UndefinedIndexException e) {
		    Logger.getLogger(StackInterpreter.class.getName()).log(Level.WARNING, "Operand of PUSH not found", e);
		    c1 = e.getDefaultNode();

		}


		seen.add(op);
		stack.push(c1);

		break;
	    case CLUSTER:
		//take the two elements from the stack
		//they are now in a cluster.
		//push that cluster back onto the stack.

                
		if (stack.size() < 2) {
                        //can't cluster less than 2 items. treat this as a PUSH operand
                    
                    //don't do anything if the operand's been seen.
                    if(seen.contains(op)){
                            wastage++;
                            break;
                    }

                    try {
                                c1 = ClusterUtils.lookupIndexInTree(op, tree);
                    } catch (UndefinedIndexException e) {
                                Logger.getLogger(StackInterpreter.class.getName()).log(Level.WARNING, "Operand of PUSH not found", e);
                                c1 = e.getDefaultNode();
                    }


                    stack.push(c1);
                    seen.add(op);
			
		    break;
		}

		c1 = stack.pop();
		c2 = stack.pop();



		if (c1.equals(c2)) {
		    break;
		}






		ClusterNode parent1, parent2;

		parent1 = ClusterUtils.findParent(c1, tree);
		parent2 = ClusterUtils.findParent(c2, tree);
		
		if(parent1 == parent2 && parent1 != tree){
			break;
		}

		ClusterNode newParent = ClusterUtils.addEmptyIgnoreID(tree, tree);

		newParent.addChild(c1);
		newParent.addChild(c2);
		
		ClusterUtils.move(c1, parent1, newParent, tree, false);
		ClusterUtils.move(c2, parent2, newParent, tree, false);

		Logger.getLogger(StackInterpreter.class.getName()).log(Level.FINEST, String.format("CLUSTER (%s) (%s)", c1, c2));
		stack.push(newParent);
		break;

	    case NOP:
	    default:
		break;

	}
    }
}
